/*
 * File:   semantics.h
 * Author: simpatico
 *
 * Created on June 4, 2010, 4:01 PM
 */

#ifndef SEMANTICS_H
#define	SEMANTICS_H
#include <string.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include "functlist.h"
extern int lineNumber;
extern const char* RET;

void yyerror(const char * errorMsg) {
    //use stderr to print on the "error output" stream
    fprintf(stderr, "error at line: %d: %s\n", lineNumber, errorMsg);
    exit(-1);
}

void printList(const list *n, const bool varr) {
    if (!n) printf("empty list\n");

    while (n != NULL) {
        if (varr) {
            const var *v = (var*) n->data;
            if (v) printf("print var %s %s returned: %i next: %p \n", v->type, v->name, v->returned, n->next);
        } else {
            const funct * f = (funct*) n->data;
            const list* args = f->args;
            const list* body = f->body;
            printf("print funct %s %s \n",f->type, f->name);

            if (args) printList(args, true);

            printf("body:{\n");
            if (body) printList(body, true);

            printf("}\n");
        }
        n = n->next;
    }
}

bool isMain(const char functionName[]) {
    assert(functionName);
    if (strncmp(functionName, "main", 5)) return false;
    return true;
}

bool validArgs(const char *type, const list* args, const char* type1) {
    if (args == NULL || args->data == NULL || args->next == NULL || args->next->data == NULL) return false;
    var* arg = (var*) args->data;
    var* arg1 = (var*) args->next->data;
    assert(arg && arg1 && arg->type && arg1->type);
    if (strncmp(arg->type, type, 100) == 0 && strncmp(arg1->type, type1, 100) == 0) return true;
    return false;
}

void checkMain(const list* fList, const list* fNode) {
    const funct* f = (funct *) fNode->data;
    if (isMain(f->name)) {
        if (findNode(fList, f->name, false)) yyerror("2nd main found");
        assert(f->type);
        if (strncmp(f->type, "void", 100) == 0 || strncmp(f->type, "int", 100) == 0) {
            if (f->args == NULL || validArgs("string", f->args, "int")) return; //inverted due to args list
        }
        yyerror("invalid main arguments");
    }
}

bool checkTypes1(const char * type1, const char * type2){
    assert(type1 && type2);

    //compare the two types
		if(strncmp(type1, type2, 100) == 0)
			return true;
		//special casting rules
		else if(strncmp(type2, "float", 100) == 0 && strncmp(type1, "int", 100) == 0) //casting from int to float
			 return true;
		return false;
}

bool checkTypes(const var* v1, const var* v2) {
    //Null pointer checks
    if (!v1 || !v2) {
        yyerror("Variable not found");
    }
    if (!v1->type || !v2->type) return false;
    return checkTypes1(v1->type, v2->type);
}

void checkReturn(const list* fNode) {
    const funct* f = (funct*) fNode->data;
    const char* rType = f->type;
    assert(rType);
    const bool retRequired = strncmp(rType, "void", 100) != 0;

    bool retFound = false;
    const list* vars = f->body;
    printList(vars, true);
    while (vars) {
        const var *v = (var*) vars->data;
        assert(v);
        bool wasEmpty = false;
        if (v->returned) {
            if (!retRequired) { //don't join in a single if &&
                if (v->type) yyerror("invalid return type for void function.\n");
                wasEmpty = true;
            } else if (!checkTypes1(v->type, rType)) yyerror("invalid type for non void function\n");
            if (!wasEmpty) retFound = true;
        }
        vars = vars->next;
    }
    if (retRequired && retFound) return;
    if (!retRequired && !retFound) return;

    char s[100] = "invalid return for function with return type: ";
    strncat(s, rType, 100);
    strncat(s, ", while a(n invalid) return ", 100);
    strncat(s, (retFound ? "was " : "was not"), 100);
    strncat(s, " found\n", 100);
    yyerror(s);
}

bool varEquals(const var* v, const var* v1) {
    if (!v && !v1) return true; //two nulls are equal
    //values comparison
    if (v && v1) {
        if (checkTypes(v, v1)) {
            if (strncmp(v->type, "int", 100) == 0) {
                if (v->value.i == v1->value.i) return true;
            } else if (strncmp(v->type, "float", 100) == 0) {
                if (v->value.f == v1->value.f) return true;
            } else if (strncmp(v->type, "string", 100) == 0) {
                if (strncmp(v->value.s, v1->value.s, 100) == 0) return true;
            }else if (strncmp(v->type, "char", 100) == 0) {
                if (v->value.c == v1->value.c) return true;
            } else if (strncmp(v->type, "bool", 100) == 0) {
                if (v->value.b == v1->value.b) return true;
            }
            return false;
        }
    }
    return false;
}

bool varLess(const var* v, const var* v1) {
    if (!v && !v1) return true; //two nulls are equal
    //values comparison
    if (v && v1) {
        if (checkTypes(v, v1)) {
        	if (strncmp(v->type, "int", 100) == 0) {
        	    if (v->value.i < v1->value.i) return true;
        	 } else if (strncmp(v->type, "float", 100) == 0) {
                 if (v->value.f < v1->value.f) return true;
             } else if (strncmp(v->type, "string", 100) == 0) {
                 if (strncmp(v->value.s, v1->value.s, 100) < 0) return true;
        	 }else if (strncmp(v->type, "char", 100) == 0) {
                 if (v->value.c < v1->value.c) return true;
        	  } else if (strncmp(v->type, "bool", 100) == 0) {
        	     if (v->value.b < v1->value.b) return true;
        	  }
        	  return false;
        }
    }
    return false;
}

bool varGreater(const var* v, const var* v1) {
    if (!v && !v1) return true; //two nulls are equal
    //values comparison
    if (v && v1) {
        if (checkTypes(v, v1)) {
        	if (strncmp(v->type, "int", 100) == 0) {
        	    if (v->value.i > v1->value.i) return true;
        	} else if (strncmp(v->type, "float", 100) == 0) {
        	    if (v->value.f > v1->value.f) return true;
        	} else if (strncmp(v->type, "string", 100) == 0) {
        	    if (strncmp(v->value.s, v1->value.s, 100) > 0) return true;
        	}else if (strncmp(v->type, "char", 100) == 0) {
        	    if (v->value.c > v1->value.c) return true;
        	} else if (strncmp(v->type, "bool", 100) == 0) {
        	    if (v->value.b > v1->value.b) return true;
        	}
        	return false;
        }
    }
    return false;
}



var* findVar(const char * name, const list * vRoot) {
    var * var = checkVar(vRoot, name);

    if (var)
        return var;

    char s[100] = "Identifier \"";
    strncat(s, name, 100);
    strncat(s, "\" not found", 100);
    yyerror(s);
}

list* mergeVars(const list* root, const list* vList) {
    if (root) {
        const list* tmp = root;
        while (tmp) {
            const var* v = (const var*) tmp->data;
            if (v->name && findNode(vList, v->name, true)) {
                char s[100] = "duplicate variable declaration found";
                strncat(s, v->name, 100);
                yyerror(s);
            }

            tmp = tmp->next;
        }
    }
    list_add((list*) root, vList);
}

void findFunct1(const list* fList, const funct*f) {
    assert(f && f->name && f->type);

    bool found = false;
    const bool couldBeFound = findNode(fList, f->name, false);

    const list* tmp = fList;
    while (!found && couldBeFound && fList) {
        const funct* foo = (funct*) fList->data;
        assert(foo && foo->name && foo->type); //no dummy functions in fList
        if ((strncmp(foo->name, f->name, 100) == 0) && (strncmp(foo->type, f->type, 100) == 0)) {
            if (foo->args && f->args) {
                found = true;
            }
        }
        tmp = tmp->next;
    }
    if (found) return;

    char s[100] = "calling undefined function ";
    strncat(s, f->name, 100);
    yyerror(s);
}

void findFunct(const list* fList, const char* fName, const list* vars) {
    if (!fList) yyerror("no declared function  yet.\n");
    assert(fName);

    printf("inside findFunct\n");
    printf("fName %s\t",fName);
    printf("vars: \n");
    printList(vars, true);
    printList(fList, false);


    bool found = false;
    const bool couldBeFound = findNode(fList, fName, false);

    const list* tmp = fList;

    while (tmp && !found && couldBeFound) {
        const funct* foo = (funct*) tmp->data;
        if (strncmp(fName, foo->name, 100) == 0){
            const list* args = foo->args;

            bool sameArgs = (!vars && !args) || (vars && args);

            while (sameArgs && vars && args) {

                var* arg = (var*) args->data;
                assert(arg && arg->type);

                var* v = (var*) vars->data;
                assert(v && v->type);

                if (strncmp(v->type, arg->type, 100) != 0){
                    sameArgs = false;
                    assert(!found);
                }

                args = args->next;
                vars = vars->next;
            }
            if (sameArgs){
                assert(!found);
                assert(couldBeFound);
                printf("I've found it. Foo name: %s \n", foo->name);
                found = true;
            }
        }
        tmp = tmp->next;
    }
    if (found) {
        printList(fList, false);
        return;
    }
    char s[100] = "calling undefined function ";
    strncat(s, fName, 100);
    yyerror(s);
}

const list* addFunction(const list* fList, const char rtype[], const list* args, const char name[], const list* body) {
    assert(rtype);
    list* fNode = newNode(rtype, args, name, NULL, false, body);
    assert(fNode);
    checkMain(fList, fNode);
    checkReturn(fNode);
    if (findNode(fList, name, false)) {
        char s[100] = "multiple definition of function ";
        strncat(s, name, 100);
        yyerror(s);
    } else {
        fList = list_add(fList, fNode); //else because if yerror doesn't include exit
    }
    return fList;
}

const list* addVar1(const list* root, const var* v) {
    const list* node = newN((const int*) v);
    return mergeVars(root, node);
}

const list* addVar(const char* type, const list* root, const char name[]) {
    if (findNode(root, name, true)) yyerror("multiple definition of argument ");
    else {
        const var* v = newVarWithoutValue(type, name, NULL);
        root = addVar1(root, v);
    }
    return root;
}

var* findOrCreateVar(const char * name, const list * vRoot) {
    var * var = checkVar(vRoot, name);
    if (var)
        return var;
    else
        return newVarWithoutValue(NULL, name, NULL);
}

const list* addVarWithType(const char* type, const list* root, const char name[]) {
    root = addVar(type, root, name);
    var * v = (var*) root->data;
    v->type = (char *) type;
    return root;
}

void printAll(const list* args, const list* vRoot, const list* dollar) {
    printf("\n~~~start of print\nargs:\n");
    printList(args, true);
    printf("vRoot:\n");
    printList(vRoot, true);
    printf("dollar:\n");
    printList(dollar, true);
    printf("~~~end of print\n\n\n");
}

void printAll2(const list* args, const list* vRoot, const var* dollar) {
    printf("\n~~~start of print\nargs:\n");
    printList(args, true);
    printf("vRoot:\n");
    printList(vRoot, true);
    printf("$$:\n");
    printf("%i\n", dollar->value.i);
    printf("~~~end of print\n\n\n");
}

var* newVarWithValue(const char * type, const char * name, union value val){
	assert(type);
	var* v;
    if (strncmp(type, "int", 4) == 0) {
        v = newVarInt(name, val.i);
    } else if (strncmp(type, "float", 6) == 0) {
        v = newVarFloat(name, val.f);
    } else if (strncmp(type, "char", 5) == 0) {
        v = newVarChar(name, val.c);
    } else if (strncmp(type, "string", 7) == 0) {
        v = newVarString(name, val.s);
    } else {
    	yyerror("type unknown");
    }
    return v;
}

var* assign(const var* operator1, const var * operator2) {
    const char * type = operator2->type;
    assert(type);

    if (strlen(type) < 1) {
        char s[100] = "type of variable ";
        strncat(s, operator1->name, 100);
        strncat(s, " is incorrect, declared type: ", 100);
        strncat(s, type, 100);
        yyerror(s);
    } //note: type should also not be void

    if (operator1->typeQualifier && strncmp(operator1->typeQualifier, "const", 6) == 0) {
        char s[100] = "you cannot assign a value to the const ";
        strncat(s, type, 100);
        strncat(s, " variable \"", 100);
        strncat(s, operator1->name, 100);
        strncat(s, "\"", 100);
        yyerror(s);
    }

    var* v;
    if (strncmp(type, "int", 4) == 0) {
        v = newVarInt(operator1->name, operator2->value.i);
    } else if (strncmp(type, "float", 6) == 0) {
        v = newVarFloat(operator1->name, operator2->value.f);
    } else if (strncmp(type, "char", 5) == 0) {
        v = newVarChar(operator1->name, operator2->value.c);
    } else if (strncmp(type, "string", 7) == 0) {
        v = newVarString(operator1->name, operator2->value.s);
    } else if (strncmp(type, "bool", 100) == 0) {
        v = newVarBool(operator1->name, operator2->value.b);
    }
    v->type = operator2->type;
    return v;
}

void checkAssignment(const list* vRoot, const var * v) {
    assert(v);
    var * t = findVar(v->name, vRoot);
    if (!t)
        yyerror("variable not found");

     if(!checkTypes1(v->type, t->type)){
    	 char s[100] = "type of \"";
         strncat(s, v->name, 100);
         strncat(s, "\" is not compatible with ", 100);
         strncat(s, t->type, 100);
    	 yyerror(s);
     }
}

void setType(const list * varList, const char* type) {
    assert(type);

    const list * l = varList;
    var * p;
    while (l != NULL) {
        p = (var*) (l->data);
        assert(p->name);
        if (p->type) {
            if (!checkTypes1(p->type, type)) {
                char s[100] = "type of \"";
                strncat(s, p->name, 100);
                strncat(s, "\" ", 100);
                strncat(s, p->type, 100);
                strncat(s, " ", 100);
                strncat(s, "is not compatible with ", 100);
                strncat(s, type, 100);
                yyerror(s);
            }
        }
        p->type = (char*) type;
        l = l->next;
    }
}

void setType1(const list * varList, const char* type, const char* typeQualifier) {
    assert(type);
    const list * l = varList;
    var * p;
    while (l != NULL) {
        p = (var*) (l->data);
        assert(p->name);
        if (p->type) {
            if (!checkTypes1(p->type, type)) {
                char s[100] = "type of \"";
                strncat(s, p->name, 100);
                strncat(s, "\" ", 100);
                strncat(s, p->type, 100);
                strncat(s, " ", 100);
                strncat(s, "is not compatible with ", 100);
                strncat(s, type, 100);
                yyerror(s);
            }
        }
        p->type = (char*) type;
        p->typeQualifier = typeQualifier;
        l = l->next;
    }
}


#endif	/* SEMANTICS_H */
